/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package base.node;

import utilities.DrawingBean;
import java.awt.Point;
import java.util.Date;
import java.util.Random;
import java.util.Vector;
import models.Losses;
import models.Mobility;
import simulation.GUIOptionManager;

/**
 * A daemon for the nodes, aiming at custom operations
 * @author artem, Arvanitis Ioannis
 */
public class Daemon {

    private int transmitterId;
    private static int[] nodesPerCategory = new int[GUIOptionManager.getNodeArchPerCategory().size()];
    private final int km = 1000;

    public Daemon() {
    }

    public Daemon(int transmitterId) {
        this.transmitterId = transmitterId;
    }

    /**
     * Finds the nodes that are located within a specific range
     * @return the nodes that are located within a specific range
     */
    public Vector<Integer> findNodesInRange() {
        int frequency = GUIOptionManager.getFrequency();
        Vector<Integer> tempv = new Vector<Integer>();
        for (int i = 0; i < GUIOptionManager.getWsnodes().length; i++) {
            if (transmitterId != i + 1) {
                double dist = findDistance(transmitterId, i + 1);
                double losses = Losses.getFreeSpacePathLoss(dist, frequency);
                losses = Losses.getResultInDecibels(Losses.getFreeSpacePathLoss(dist, frequency), true);
                //check free space loss
                if (GUIOptionManager.getWsnodes(transmitterId - 1).getNodeInfo().getAntennaRadPow() - losses > GUIOptionManager.getWsnodes(i).getNodeInfo().getAntennaThres()) {
                    //check rain loss in case of low FSL
                    if (GUIOptionManager.getRain() == null) {
                        tempv.addElement(Integer.valueOf(i + 1));
                    } else {
                        losses += getRainLosses(transmitterId, i + 1);
                        if (GUIOptionManager.getWsnodes(transmitterId - 1).getNodeInfo().getAntennaRadPow() - losses > GUIOptionManager.getWsnodes(i).getNodeInfo().getAntennaThres()) {
                            tempv.addElement(Integer.valueOf(i + 1));
                        }
                    }
                }
            }
        }
        for (int i = 0; i < tempv.size(); i++) {
            System.out.print(tempv.get(i).intValue() + " ");
        }
        System.out.println();
        return tempv;
    }

    /**
     * Finds the distance of two nodes (in meters)
     * @param id1 Node's id 1
     * @param id2 Node's id 2
     * @return the distance of two nodes (in meters)
     */
    private double findDistance(int id1, int id2) {
        Point p1 = GUIOptionManager.getNodes().get(id1 - 1).getPoint();
        Point p2 = GUIOptionManager.getNodes().get(id2 - 1).getPoint();
        return findDistance(p1.x, p1.y, p2.x, p2.y);
    }

    /**
     * Finds the distance of two points (in meters)
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @return the distance of two points (in meters)
     */
    private double findDistance(double x1, double y1, double x2, double y2) {
        double dx_m = Math.abs(x1 - x2);
        double dy_m = Math.abs(y1 - y2);
        return Math.sqrt(Math.pow(dx_m, 2) + Math.pow(dy_m, 2));
    }

    /**
     * Gets the rain absorption from the node with id1 to node with id2
     * @param id1 The id of the first node
     * @param id2 The id of the second node
     * @return the rain absorption between the two nodes
     */
    private double getRainLosses(int id1, int id2) {
        Point p1 = GUIOptionManager.getNodes().get(id1 - 1).getPoint();
        Point p2 = GUIOptionManager.getNodes().get(id2 - 1).getPoint();
        float cell_width_meters = GUIOptionManager.getMapWidthMeters() / GUIOptionManager.getMapWidthRain();
        float cell_height_meters = GUIOptionManager.getMapHeightMeters() / GUIOptionManager.getMapHeightRain();
        Point c1 = new Point((int) Math.floor(((float) p1.x) / cell_width_meters), (int) Math.floor(((float) p1.y) / cell_height_meters));
        Point c2 = new Point((int) Math.floor(((float) p2.x) / cell_width_meters), (int) Math.floor(((float) p2.y) / cell_height_meters));
        Point src, dst, current_cell;
        if (p1.x <= p2.x) {
            if (p1.x == p2.x) {
                if (p1.y < p2.y) {
                    src = c1;
                    dst = c2;
                } else {
                    src = c2;
                    dst = c1;
                    current_cell = new Point(p1);
                    p1 = new Point(p2);
                    p2 = new Point(current_cell);
                }
            } else {
                src = c1;
                dst = c2;
            }
        } else {
            src = c2;
            dst = c1;
            current_cell = new Point(p1);
            p1 = new Point(p2);
            p2 = new Point(current_cell);
        }
        current_cell = new Point(src.x, src.y);
        double rainLosses = 0;
        //vertical route
        if (p1.x == p2.x) {
            if (src.x == dst.x && src.y == dst.y) {
                rainLosses += findRainLossPerMPerCell(src) * findDistance(p1.x, p1.y, p2.x, p2.y);
            } else {
                rainLosses += findRainLossPerMPerCell(src) * findDistance(p1.x, p1.y, p1.x, (src.y + 1) * cell_height_meters);
                for (current_cell.y += 1; current_cell.y < dst.y; current_cell.y += 1) {
                    rainLosses += findRainLossPerMPerCell(current_cell) * cell_height_meters;
                }
                rainLosses += findRainLossPerMPerCell(dst) * findDistance(p2.x, p2.y, p2.x, dst.y * cell_height_meters);
            }
        } //horizontal route
        else if (p1.y == p2.y) {
            if (src.x == dst.x && src.y == dst.y) {
                rainLosses += findRainLossPerMPerCell(src) * findDistance(p1.x, p1.y, p2.x, p2.y);
            } else {
                rainLosses += findRainLossPerMPerCell(src) * findDistance(p1.x, p1.y, (src.x + 1) * cell_width_meters, p1.y);
                for (current_cell.x += 1; current_cell.x < dst.x; current_cell.x += 1) {
                    rainLosses += findRainLossPerMPerCell(current_cell) * cell_width_meters;
                }
                rainLosses += findRainLossPerMPerCell(dst) * findDistance(p2.x, p2.y, dst.y * cell_width_meters, p2.y);
            }
        } //custom route: y=ax+b
        else {
            float a, b;
            a = ((float) (p1.y - p2.y)) / (p1.x - p2.x);
            b = p1.y - a * p1.x;
            double x1, y1, x2, y2;
            double x1_intersection, y1_intersection, x2_intersection, y2_intersection;
            x1_intersection = p1.x;
            y1_intersection = p1.y;
            while (current_cell.x != dst.x && current_cell.y != dst.y) {
                x1 = (current_cell.x + 1) * cell_width_meters;
                y1 = a * x1 + b;
                y2 = ((a > 0) ? (current_cell.y + 1) : current_cell.y) * cell_height_meters;
                x2 = (y2 - b) / a;
                if (x1 < x2) {
                    x2_intersection = x1;
                    y2_intersection = y1;
                    rainLosses += findRainLossPerMPerCell(current_cell) * findDistance(x1_intersection, y1_intersection, x2_intersection, y2_intersection);
                    x1_intersection = x2_intersection;
                    y1_intersection = y2_intersection;
                    current_cell.x += 1;
                } else if (x1 > x2) {
                    x2_intersection = x2;
                    y2_intersection = y2;
                    rainLosses += findRainLossPerMPerCell(current_cell) * findDistance(x1_intersection, y1_intersection, x2_intersection, y2_intersection);
                    x1_intersection = x2_intersection;
                    y1_intersection = y2_intersection;
                    current_cell.y = (a > 0) ? (current_cell.y + 1) : (current_cell.y - 1);
                } else {
                    x2_intersection = x1;
                    y2_intersection = y1;
                    rainLosses += findRainLossPerMPerCell(current_cell) * findDistance(x1_intersection, y1_intersection, x2_intersection, y2_intersection);
                    x1_intersection = x2_intersection;
                    y1_intersection = y2_intersection;
                    current_cell.x += 1;
                    current_cell.y = (a > 0) ? (current_cell.y + 1) : (current_cell.y - 1);
                }
            }
            x2_intersection = p2.x;
            y2_intersection = p2.y;
            rainLosses += findRainLossPerMPerCell(current_cell) * findDistance(x1_intersection, y1_intersection, x2_intersection, y2_intersection);
        }
        return rainLosses;
    }

    /**
     * Finds the rain absorption per m for a specific cell indicated by p
     * @param p The indicated cell to check
     * @return the rain absorption per m for a specific cell indicated by p
     */
    private double findRainLossPerMPerCell(Point p) {
        Vector<DrawingBean> tempv = GUIOptionManager.getRain();
        for (int i = 0; i < tempv.size(); i++) {
            if (tempv.get(i).getPoint().x == p.x && tempv.get(i).getPoint().y == p.y) {
                return Double.parseDouble(tempv.get(i).getInfo()) / km;
            }
        }
        return 0;
    }

    /**
     * Moves the nodes depending on the model used
     */
    public void move() {
        Point p;
        if (!GUIOptionManager.isRpgm()) {
            p = GUIOptionManager.getNodes().get(transmitterId - 1).getPoint();
            p = new Point(Mobility.getRandomWaypoint(p));
            GUIOptionManager.getWsnodes(transmitterId - 1).getNodeInfo().setPosition(p);
            GUIOptionManager.getNodes().get(transmitterId - 1).setPoint(p);
        } else {
            int category = Integer.parseInt(GUIOptionManager.getNodes().get(transmitterId - 1).getInfo());
            nodesPerCategory[category - 1] += 1;
            if (nodesPerCategory[category - 1] == GUIOptionManager.getNodeArchPerCategory().get(category - 1).getNumberOfNodes()) {
                nodesPerCategory[category - 1] = 0;
                Random generator = new Random();
                int grid = GUIOptionManager.getGrid();
                float cell_w, cell_h;
                if (GUIOptionManager.isInplace()) {
                    cell_w = ((float) GUIOptionManager.getMapWidthMeters()) / grid;
                    cell_h = ((float) GUIOptionManager.getMapHeightMeters()) / grid;
                } else { //50% overlap
                    cell_w = 2 * (((float) GUIOptionManager.getMapWidthMeters()) / (grid + 1));
                    cell_h = 2 * (((float) GUIOptionManager.getMapHeightMeters()) / (grid + 1));
                }
                for (int i = 0; i < grid * grid; i++) {
                    int groupOriented = (int) Math.floor(5 * generator.nextDouble());
                    int groupVelocity = (int) Math.ceil(GUIOptionManager.getMaxVelocity() * generator.nextDouble());
                    for (int j = 0; j < GUIOptionManager.getNodes().size(); j++) {
                        if (Integer.parseInt(GUIOptionManager.getNodes().get(j).getInfo()) == category) {
                            p = GUIOptionManager.getNodes().get(j).getPoint();
                            int x = i % grid;
                            int y = i / grid;
                            if (p.x >= x * cell_w && p.x < (x + 1) * cell_w && p.y >= y * cell_h && p.y < (y + 1) * cell_h) {
                                p = new Point(Mobility.getReferencePointGroupMobility(p, groupOriented, groupVelocity));
                                GUIOptionManager.getWsnodes(j).getNodeInfo().setPosition(p);
                                GUIOptionManager.getNodes().get(j).setPoint(p);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Prints custom node messages
     * @param s The string to be printed
     */
    public void print(String s) {
        Date date = new Date();
        System.out.println(" ::DAEMON:: " + date.getTime() + "\tDaemon " + transmitterId + "\t" + s);
    }
}
